import { merge } from "webpack-merge";
import "colors";
import injectEnvVariables from "./webpack.env";
import fs from "fs";
import path from "path";
import type { Configuration } from "webpack";
import serverConfig from "./webpack.devserver";

// 通用方法定义
type IsCommonModuleFunction = (fileName: string) => boolean;

const isDevModule = (fileName: string) => fileName.endsWith(".dev.ts");
const isProdModule = (fileName: string) => fileName.endsWith(".prod.ts");
const devModuleName = (fileName: string) => path.basename(fileName, ".dev.ts");
const prodModuleName = (fileName: string) =>
  path.basename(fileName, ".prod.ts");
const commonModuleName = (fileName: string) => path.basename(fileName, ".ts");
const importModule = (fileName: string) =>
  // eslint-disable-next-line @typescript-eslint/no-require-imports
  require("./modules/" + path.basename(fileName, ".ts"))
    .default as Configuration;

function loadModuleConfigs(isProd: boolean) {
  const files = fs.readdirSync(path.resolve(__dirname, "modules"));
  const maps = new Map<string, Configuration>();

  const environmentModuleName = isProd ? prodModuleName : devModuleName;
  const isEnvironmentModule = isProd ? isProdModule : isDevModule;
  let isCommonModule: IsCommonModuleFunction;

  if (isProd) {
    isCommonModule = (fileName) => !isDevModule(fileName);
  } else {
    isCommonModule = (fileName) => !isProdModule(fileName);
  }

  files.forEach((fileName) => {
    // 判断是否是对应环境的配置文件
    if (isEnvironmentModule(fileName)) {
      console.log(`加载 webpack 配置 ${fileName} ...`.blue.bold);
      // 获取对应环境的模块名称
      const moduleName = environmentModuleName(fileName);
      const commonConfig = maps.get(moduleName);
      const envConfig = importModule(fileName);

      // 如果模块不存在，则创建
      if (commonConfig == null) {
        maps.set(moduleName, envConfig);
      }
      // 环境的配置覆盖通用配置
      else {
        // *.prod.ts or *.dev.ts 覆盖 *.ts 通用配置
        // commonConfig 是通用配置，envConfig 是对应环境配置
        // 将 commonConfig 与 envConfig 进行合并，最新使用 envConfig 的值。
        maps.set(moduleName, merge(commonConfig, envConfig));
      }
    }
    // 判断是否是通用配置文件
    else if (isCommonModule(fileName)) {
      console.log(`加载 webpack 配置 ${fileName} ...`.blue.bold);
      // 获取通用配置模块名称
      const moduleName = commonModuleName(fileName);
      const envConfig = maps.get(moduleName);
      const commonConfig = importModule(fileName);

      // 如果模块不存在，则创建
      if (envConfig == null) {
        maps.set(moduleName, commonConfig);
      }
      // 环境的配置覆盖通用配置
      else {
        // *.prod.ts or *.dev.ts 覆盖 *.ts 通用配置
        // commonConfig 是通用配置，envConfig 是对应环境配置
        // 将 commonConfig 与 envConfig 进行合并，最新使用 envConfig 的值。
        maps.set(moduleName, merge(commonConfig, envConfig));
      }
    }
  });

  return Array.from(maps.values());
}

function filter(configs: Configuration[], configNames: string[]) {
  if (configNames.length > 0) {
    return configs.filter(
      (config) =>
        configNames.find((configName) => configName === config.name) !==
        undefined
    );
  }
  return configs;
}

export default function buildConfigs(
  isProd: boolean,
  isDebug: boolean,
  configNames: string[]
) {
  // eslint-disable-next-line @typescript-eslint/no-require-imports
  const commonConfig = require("./webpack.common").default;
  console.log(`加载 webpack 配置 webpack.common.ts ...`.blue.bold);
  let configs = loadModuleConfigs(isProd);

  for (let index = 0; index < configs.length; index++) {
    // 注入环境变量配置文件
    const config = configs[index];
    injectEnvVariables(config, isProd);
    // 将 commonConfig 配置与 config 合并，最新值使用 config 的值。
    configs[index] = merge(commonConfig, config);
  }

  // 进行过滤掉不需要构建的配置
  configs = filter(configs, configNames);

  if (configs.length > 0 && isDebug) {
    console.log(`加载 webpack 配置 webpack.devserver.ts ...`.bgRed.blue.bold);
    // 调试配置，只能附加到其中一个配置项，否则 webpack server 会报错。
    // 将 serverConfig 配置与 configs[0] 合并，最新值使用 configs[0] 的值。
    configs[0] = merge(serverConfig, configs[0]);
  }

  console.log(
    `完成加载 ${configs.length} 个模块 webpack 配置！`.bgGreen.blue.bold
  );

  return configs;
}
